package br.com.infocruz.seam.crm.ejb.controller;

import java.io.Serializable;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Begin;
import org.jboss.seam.annotations.End;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.datamodel.DataModel;
import org.jboss.seam.annotations.datamodel.DataModelSelectionIndex;
import org.jboss.seam.core.Conversation;
import org.jboss.seam.framework.Controller;
import org.jboss.seam.framework.EntityNotFoundException;
import org.jboss.seam.international.StatusMessages;
import org.jboss.seam.international.StatusMessage.Severity;
import org.jboss.seam.log.Log;

import br.com.infocruz.seam.crm.ejb.facade.Facade;
import br.com.infocruz.seam.crm.ejb.model.AbstractEntity;
import br.com.infocruz.seam.crm.ejb.model.EntityList;

@Scope(ScopeType.CONVERSATION)
public abstract class AbstractController<T extends AbstractEntity, F extends Facade<T>> extends Controller {
	/**
	 * 
	 */
	private static final long serialVersionUID = -4325413443848810401L;

	public static final String LIST = "list";
	public static final String VIEW = "view";
	public static final String CREATE = "create";
	public static final String PERSISTED = "persisted";
	public static final String EDIT = "edit";
	public static final String UPDATED = "updated";
	public static final String REMOVED = "removed";

	@Logger
	protected Log log;

	@In
	protected StatusMessages statusMessages;

	@In
	protected Conversation conversation;

	@In(required = false)
	private F facade;

	protected Serializable id;

	// @DataModelSelection
	protected T model;
	
	protected T filter;

	@DataModelSelectionIndex
	protected int selectedItemIndex;

	@In
	protected StatusMessages facesMessages;

	private Class<T> entityClass;

	private Class<F> facadeClass;

	protected EntityList<T> entityList = new EntityList<T>();

	public AbstractController(Class<T> entityClass, Class<F> facadeClass) {
		this.entityClass = entityClass;
		this.facadeClass = facadeClass;
	}

	public void reset() {
		model = null;
		filter = null;
		entityList = new EntityList<T>();
	}

	public Serializable getId() {
		return id;
	}

	public void setId(Serializable id) {
		this.id = id;
	}

	@SuppressWarnings("unchecked")
	protected F getFacade() {
		if (facade == null) {
			facade = (F) Component.getInstance(StringUtils.uncapitalize(facadeClass.getSimpleName()));
		}
		return facade;
	}

	public T getSelected() {
		return getModel();
	}

	public T getModel() {
		if (model == null) {
			model = createModel();
		}		
		return model;
	}

	private T createModel() {
		try {
			return entityClass.newInstance();
		} catch (Exception e) {
			log.error(e);
			throw new RuntimeException(e);
		}
	}

	public void setModel(T model) {
		this.model = model;
	}

	public T getFilter() {
		if (filter == null) {
			filter = createModel();
		}
		return filter;
	}

	public void setFilter(T filter) {
		this.filter = filter;
	}

	public EntityList<T> getEntityList() {
		return entityList;
	}

	public void setEntityList(EntityList<T> entityList) {
		this.entityList = entityList;
	}

	protected String refresh() {
		return search();
	}

	public String list() {
		List<T> resultList = getFacade().findRange(getMaxResults(), getFirstResult(), getOrderDirection(), getOrderColumn());
		Integer resultCount = getFacade().count();
		refreshResults(resultList, resultCount);
		return LIST;
	}

	public String search() {
		List<T> resultList = getFacade().findByExample(getFilter(), getMaxResults(), getFirstResult(), getOrderDirection(), getOrderColumn(), null);
		Integer resultCount = getFacade().countByExample(getFilter(), null);
		refreshResults(resultList, resultCount);
		return LIST;
	}

	protected void refreshResults(EntityList<T> entityList) {
		refreshResults(entityList.getResultList(), entityList.getResultCount());
	}

	protected void refreshResults(List<T> resultList, Integer resultCount) {
		this.entityList.setResultList(resultList);
		this.entityList.setResultCount(resultCount);
	}

	public String view() {
		model = getFacade().find(id);
		return VIEW;
	}

	@Begin(join = true)
	public String prepareCreate() {
		model = createModel();
		return CREATE;
	}

	@End
	public String create() {
		try {
			getFacade().create(getModel());
			createMessage();
			return PERSISTED;
		} catch (Exception e) {
			handleException(e);
			return null;
		}
	}

	@End
	public String persist() {
		return create();
	}

	@Begin(join = true)
	public String prepareEdit() {
		model = getFacade().find(id);
		return EDIT;
	}

	@End
	public String update() {
		try {
			getFacade().update(getSelected());
			updateMessage();
			return UPDATED;
		} catch (Exception e) {
			handleException(e);
			return null;
		}
	}

	@End
	public String remove() {
		performRemove();
		reset();
		return REMOVED;
	}

	@End
	public String removeAndView() {
		performRemove();
		reset();
		return VIEW;
	}

	private void performRemove() {
		try {
			getFacade().remove(getSelected());
			removeMessage();
		} catch (Exception e) {
			handleException(e);
		}
	}

	protected void handleException(Exception e) {
		statusMessages.addFromResourceBundle(Severity.ERROR, "controller.persistenceErrorOccured", e.getMessage());
	}

	protected void createMessage() {
		statusMessages.clearGlobalMessages();
		statusMessages.addFromResourceBundle(Severity.INFO, "controller.entityCreated", entityClass.getSimpleName());
	}

	protected void updateMessage() {
		statusMessages.clearGlobalMessages();
		statusMessages.addFromResourceBundle(Severity.INFO, "controller.entityUpdated", entityClass.getSimpleName());
	}

	protected void removeMessage() {
		statusMessages.addFromResourceBundle(Severity.INFO, "controller.entityRemoved", entityClass.getSimpleName());
	}

	protected void raiseAfterTransactionSuccessEvent() {
		raiseTransactionSuccessEvent("org.jboss.seam.afterTransactionSuccess", new Object[0]);
		String simpleEntityName = entityClass.getSimpleName();
		if (simpleEntityName == null)
			return;
		raiseTransactionSuccessEvent("org.jboss.seam.afterTransactionSuccess." + simpleEntityName, new Object[0]);
	}

	// ENTITY LIST DELEGATION

	public Integer getCurrentPage() {
		return entityList.getCurrentPage();
	}

	public T getEntityInstance() {
		return entityList.getEntityInstance();
	}

	public Integer getFirstResult() {
		return entityList.getFirstResult();
	}

	public Long getLastFirstResult() {
		return entityList.getLastFirstResult();
	}

	public Integer getMaxResults() {
		return entityList.getMaxResults();
	}

	public int getNextFirstResult() {
		return entityList.getNextFirstResult();
	}

	public String getOrder() {
		return entityList.getOrder();
	}

	public String getOrderColumn() {
		return entityList.getOrderColumn();
	}

	public String getOrderDirection() {
		return entityList.getOrderDirection();
	}

	public Integer getPageCount() {
		return entityList.getPageCount();
	}

	public int getPreviousFirstResult() {
		return entityList.getPreviousFirstResult();
	}

	public String getRestrictionLogicOperator() {
		return entityList.getRestrictionLogicOperator();
	}

	public Integer getResultCount() {
		return entityList.getResultCount();
	}

	@DataModel
	public List<T> getResultList() {
		if (entityList.getResultList() == null) {
			// find();
		}
		return entityList.getResultList();
	}

	public boolean isNextExists() {
		return entityList.isNextExists();
	}

	public boolean isPaginated() {
		return entityList.isPaginated();
	}

	public boolean isPreviousExists() {
		return entityList.isPreviousExists();
	}

	public String first() {
		entityList.first();
		return search();
	}

	public String last() {
		entityList.last();
		return search();
	}

	public String next() {
		entityList.next();
		return search();
	}

	public String previous() {
		entityList.previous();
		return search();
	}

	public void setEntityInstance(T entityInstance) {
		entityList.setEntityInstance(entityInstance);
	}

	public void setFirstResult(Integer firstResult) {
		entityList.setFirstResult(firstResult);
	}

	public void setMaxResults(Integer maxResults) {
		entityList.setMaxResults(maxResults);
	}

	public void setOrder(String order) {
		entityList.setOrder(order);
	}

	public void setOrderColumn(String orderColumn) {
		entityList.setOrderColumn(orderColumn);
	}

	public void setOrderDirection(String orderDirection) {
		entityList.setOrderDirection(orderDirection);
	}

	public void setRestrictionLogicOperator(String operator) {
		entityList.setRestrictionLogicOperator(operator);
	}

	public void setResultCount(Integer resultCount) {
		entityList.setResultCount(resultCount);
	}

	public void setResultList(List<T> resultList) {
		entityList.setResultList(resultList);
	}

	@Observer("org.jboss.seam.beforePhase")
	public void beforePhase() {
	}

	@Observer("org.jboss.seam.afterPhase")
	public void afterPhase() {
	}
	
	@Observer("org.jboss.seam.beforeTransactionCompletion")
	public void beforeTransactionCompletion() {
//		getFacade().clearPersistenceContext();
	}
	
	/**
	 * Utility method called by the framework when no entity is found in the
	 * Persistence Context.
	 */
	protected T handleNotFound() {
		throw new EntityNotFoundException(getId(), entityClass);
	}

	/**
	 * Returns true if the id of the object managed is known.
	 */
	public boolean isIdDefined() {
		return getId() != null && !"".equals(getId());
	}

	public boolean isWired() {
		return getModel() != null;
	}

	public boolean isManaged() {
		return getId() != null;
	}

	public String cancelAndView() {
		reset();
		return view();
	}

	public String cancelAndList() {
		reset();
		return list();
	}

}
